﻿using CTP.CRM.Core.SharedModel.Options;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Volo.Abp.DependencyInjection;

namespace CTP.CRM.Core.SharedModel.InternalCore.Token
{
    public class DefaultTokenGeneratorService : ITokenGeneratorService, ISingletonDependency
    {
        private readonly ISecurityKeyProvider _securityKeyProvider;
        private IOptions<UserCenterTokenOption> _userCenterTokenOption;

        private readonly string ENV;
        private readonly string ISSUERSIGNINGKEY;

        public DefaultTokenGeneratorService(ISecurityKeyProvider securityKeyProvider
            , IOptions<UserCenterTokenOption> userCenterTokenOption)
        {
            _securityKeyProvider = securityKeyProvider;
            _userCenterTokenOption = userCenterTokenOption;
            ENV = _userCenterTokenOption.Value.Environment;
            ISSUERSIGNINGKEY = CTPABPCore.Configuration["JWTSettings:IssuerSigningKey"]!;
        }

        public async Task<TokenCertificateResult> GeneratorAsync(List<Claim> claims, string? expiredTime = null) => await Task.FromResult(Generator(claims, expiredTime));

        public TokenCertificateResult Generator(List<Claim> claims, string expiredTime = null, string secretKey = null)
        {
            // Build the claims list based on the provided claims
            var buildClaims = BuildClaimsList(claims);

            // Get the signing credentials using the issuer signing key
            var signingCredentials = GetSigningCredentials(ISSUERSIGNINGKEY);

            // Generate a token using the signing credentials, built claims, and expiration time (defaulting to 1440 minutes if not provided)
            string token = GenerateToken(signingCredentials, buildClaims, expiredTime ?? "1440");

            // Return a new TokenCertificateResult containing the generated token
            return new TokenCertificateResult(token);
        }

        private List<Claim> BuildClaimsList(List<Claim> s_claims)
        {
            const string KEY = "f72be52dbe70";
            AddIfMissing(s_claims, new Claim(KEY, $"{ENV}"));
            AddIfMissing(s_claims, new Claim(JwtRegisteredClaimNames.Iss, CTPABPCore.Configuration["JWTSettings:ValidIssuer"] ?? "usercenter"));
            AddIfMissing(s_claims, new Claim(JwtRegisteredClaimNames.Aud, CTPABPCore.Configuration["JWTSettings:ValidAudience"] ?? "usercenter"));
            return s_claims;
        }

        private void AddIfMissing(List<Claim> claims, Claim claim)
        {
            if (!claims.Any(x => x.Type == claim.Type))
            {
                claims.Add(claim);
            }
        }

        private static string GenerateToken(SigningCredentials signingCredentials, IEnumerable<Claim> claims, string expires = null)
        {
            // If the expires parameter is null, set it to the value from configuration
            expires ??= CTPABPCore.Configuration["JWTSettings:ExpiredTime"];

            // Create a new JWT security token with the provided claims, expiration time, and signing credentials
            var token = new JwtSecurityToken(
                claims: claims,
                expires: DateTime.UtcNow.AddMinutes(Convert.ToInt32(expires)),
                signingCredentials: signingCredentials);

            // Create a JWT security token handler
            var tokenHandler = new JwtSecurityTokenHandler();

            // Write the token as a string with the "Bearer" scheme and return it
            return $"Bearer {tokenHandler.WriteToken(token)}";
        }

        private SigningCredentials GetSigningCredentials(string key)
        {
            // If the key is null, retrieve the security key from the environment provider
            key ??= _securityKeyProvider.GetSecurityKey(ENV);

            // Convert the key to bytes
            byte[] secret = Encoding.UTF8.GetBytes(key);

            // Create a new symmetric security key using the secret bytes
            var securityKey = new SymmetricSecurityKey(secret);

            // Create and return signing credentials using the symmetric security key and HmacSha256 algorithm
            return new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
        }
    }
}
